package avicit.bdp.dms.dss.rest;

import avicit.bdp.common.service.dto.BdpTableDTO;
import avicit.bdp.common.utils.CheckParaUtils;
import avicit.bdp.common.utils.DateUtils;
import avicit.bdp.common.utils.IpUtils;
import avicit.bdp.common.utils.RequestWrapperUtils;
import avicit.bdp.common.utils.redis.RedisCacheHelper;
import avicit.bdp.core.constant.Constants;
import avicit.bdp.core.util.json.JSONUtils;
import avicit.bdp.dms.das.service.BdpDasTableService;
import avicit.bdp.dms.dss.dao.DataResourceDAO;
import avicit.bdp.dms.dss.dto.ColumnInfoDto;
import avicit.bdp.dms.dss.dto.DataResourceDTO;
import avicit.bdp.dms.dss.dto.DssLogDTO;
import avicit.bdp.dms.dss.dto.DssSubscribeDTO;
import avicit.bdp.dms.dss.dto.DssUserDTO;
import avicit.bdp.dms.dss.enums.ApiCallStatus;
import avicit.bdp.dms.dss.enums.AuditStatus;
import avicit.bdp.dms.dss.enums.LogRecordCode;
import avicit.bdp.dms.dss.enums.ResourceStatus;
import avicit.bdp.dms.dss.enums.ResourceType;
import avicit.bdp.dms.dss.service.DataResourceService;
import avicit.bdp.dms.dss.service.DssLogService;
import avicit.bdp.dms.dss.service.DssSubscribeService;
import avicit.bdp.dms.dss.service.DssUserService;
import avicit.platform6.commons.utils.ComUtil;
import avicit.platform6.core.context.ThreadContextHelper;
import avicit.platform6.core.rest.msg.ResponseMsg;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author mayanj
 * 数据服务对外API接口
 */
@RestController
@RequestMapping("/api/bdp/dms/service/")
@Api(tags = "数据服务对外API接口")
public class DssPublishRest {

    private static final Logger logger = LoggerFactory.getLogger(DssPublishRest.class);

    @Autowired
    private DataResourceService dataResourceService;
    @Autowired
    private DssUserService dssUserService;
    @Autowired
    private DssLogService dssLogService;
    @Autowired
    private DssSubscribeService subscribeService;
    @Autowired
    private BdpDasTableService tableService;
    @Autowired
    private DataResourceDAO dataResourceDao;
    @Autowired
    private RedisCacheHelper redisCacheHelper;

    private static final int API_PREFIX_LENGTH = Constants.PUBLISH_DATA_SERVICE_URL_PREFIX.length();

    /**
     * 系统调用api当日统计数 redis-key 前缀
     * redis的key命名规则  bdp:+当前系统+：+功能模块+：+关键词
     */
    private static final String REDIS_PREFIX_CALL_API_NUM_TODAY = "bdp:dss:publish:call:today:";

    ExecutorService executor = Executors.newFixedThreadPool(10);

    /**
     * 对外API接口
     *
     * @param token    用户token
     * @param param    参数
     * @param request  request
     * @param response response
     */
    @ApiOperation(value = "对外API接口")
    @RequestMapping(value = "/**", method = {RequestMethod.GET, RequestMethod.POST})
    public void publish(@RequestHeader(value = "token", required = false) String token,
                        @RequestBody(required = false) Map<String, Object> param,
                        HttpServletRequest request, HttpServletResponse response) {

        ResponseMsg<Map<String, Object>> responseMsg = new ResponseMsg<>();

        Map<String, Object> parameterMap = RequestWrapperUtils.getParameterMap(request);
        if (param != null) {
            parameterMap.putAll(param);
        }

        // 记录调用日志
        DssLogDTO logDTO = new DssLogDTO();
        logDTO.setStartTime(new Date());
        logDTO.setType(LogRecordCode.CALL_API.getCode());

        String ipAddress = IpUtils.getIpAddress(request);
        // 特殊uri处理，避免"//"开头
        String requestUri = request.getRequestURI();
        requestUri = requestUri.substring(requestUri.indexOf(Constants.PUBLISH_DATA_SERVICE_URL_PREFIX) + API_PREFIX_LENGTH);
        if (requestUri.startsWith(Constants.DOUBLE_SLASH)) {
            requestUri = requestUri.substring(1);
        }

        if (StringUtils.isEmpty(token)) {
            token = MapUtils.getString(parameterMap, "token");
        }

        logger.info("远程调用接口，开始时间: {}, 客户端ip: {}, requestUri: {}, token:{}, parameterMap:{}",
                new Date(), ipAddress, requestUri, token, parameterMap);

        boolean testApi = MapUtils.getBooleanValue(parameterMap, "testApi", false);

        DataResourceDTO resource = validResource(request, response, responseMsg, logDTO, requestUri, testApi);
        if (resource == null) {
            return;
        }

        String dataResourceId = resource.getId();
        logDTO.setDataResourceId(dataResourceId);

        // testApi参数用于区别接口是否用于测试，如果是测试接口，则不必进行校验用户和权限信息
        if (testApi) {
            logDTO.setCallUserId(ThreadContextHelper.getUserId());
            logDTO.setCallUserName(ThreadContextHelper.getUserName());
        } else if(!validTokenAndUser(token, response, responseMsg, logDTO, requestUri, dataResourceId)) {
            return;
        }

        // 资源类型为数据表
        if (resource.getType() == ResourceType.TABLE.getCode()) {
            handleDataQuery(response, responseMsg, parameterMap, logDTO, dataResourceId);
        } else {
            // 资源类型为文件
            handFileResource(request, response, responseMsg, logDTO, resource);
        }

        logger.info("远程调用接口，结束时间: {}", new Date());
    }

    private DataResourceDTO validResource(HttpServletRequest request, HttpServletResponse response,
                                          ResponseMsg<Map<String, Object>> responseMsg,
                                          DssLogDTO logDTO, String requestUri,
                                          boolean testApi) {

        // 通过分享uri查询资源信息,如果分享资源不存在，或者请求方式不一致，则提示用户 【调用接口不存在】
        DataResourceDTO query = new DataResourceDTO();
        query.setApiAddress(requestUri);
        DataResourceDTO dataResourceDTO = dataResourceService.selectOne(query);
        if (dataResourceDTO == null || !request.getMethod().equalsIgnoreCase(dataResourceDTO.getRequestMethod())) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.CALLING_INTERFACE_NOT_FOUND, response, true);
            return null;
        }

        //资源如果已下线，提示用户【调用接口已下线】
        if (!testApi && ResourceStatus.WITHDRAWN.getCode() == dataResourceDTO.getStatus()) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.CALLING_INTERFACE_OFFLINE, response, true);
            return null;
        }
        return dataResourceDTO;
    }

    private void handFileResource(HttpServletRequest request, HttpServletResponse response, ResponseMsg<Map<String, Object>> responseMsg,
                                  DssLogDTO logDTO, DataResourceDTO dataResourceDTO) {
        try {
            dataResourceService.downloadFileFromFileSystem(dataResourceDTO.getId(), dataResourceDTO, request, response);
            logDTO.setCallResult("下载文件");
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.SUCCESS, response, false);
        } catch (Exception e) {
            logger.error("call error", e);
            logDTO.setCallResult(e.getMessage());
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.SYSTEM_ERROR, response, true);
        }
    }

    private void handleDataQuery(HttpServletResponse response, ResponseMsg<Map<String, Object>> responseMsg,
                                 Map<String, Object> parameterMap, DssLogDTO logDTO, String dataResourceId) {

        int pageNo = MapUtils.getIntValue(parameterMap, "pageNo", 1);
        int pageSize = MapUtils.getIntValue(parameterMap, "pageSize", 10);

        try {
            DataResourceDTO dataResourceDTO = dataResourceService.selectByPrimaryKey(dataResourceId);
            CheckParaUtils.checkDbObjectValidity(dataResourceDTO, logger, avicit.platform6.commons.utils.StringUtils.format("没有查到数据，请确保id:{}的资源数据是否存在", dataResourceId));

            String tableId = dataResourceDTO.getTableId();
            if (StringUtils.isNotEmpty(dataResourceDTO.getProjectId())) {
                tableId = dataResourceDao.getTableIdByProjectTableId(tableId);
            }
            BdpTableDTO tableDTO = tableService.selectByPrimaryKey(tableId);
            CheckParaUtils.checkDbObjectValidity(tableDTO, logger, avicit.platform6.commons.utils.StringUtils.format("没有查到数据，请确保id:{}的表数据是否存在", tableId));

            // 检验参数是否允许
            List<ColumnInfoDto> columnInfoDtoList = JSONUtils.toList(dataResourceDTO.getColumnInfo(), ColumnInfoDto.class);
            Set<String> allowColumnSet = new HashSet<>();
            for (ColumnInfoDto columnInfoDto : columnInfoDtoList) {
                allowColumnSet.add(columnInfoDto.getName());
            }
            Set<String> paramSet = new HashSet<>(parameterMap.keySet());
            paramSet.removeAll(allowColumnSet);
            paramSet.remove("token");
            paramSet.remove("pageNo");

            paramSet.remove("pageSize");
            paramSet.remove("testApi");
            paramSet.remove("_t");
            logger.info("paramSet:{}", paramSet);
            if (CollectionUtils.isNotEmpty(paramSet)) {
                recordCallStatus(responseMsg, logDTO, ApiCallStatus.INVALID_PARAM, response, true);
                return;
            }

            Map<String, Object> dataMap = dataResourceService.getDataListFromDB(dataResourceId, pageNo, pageSize, parameterMap);
            if (MapUtils.isNotEmpty(dataMap)) {
                dataMap.remove("columnList");
                dataMap.remove("dataResource");
                dataMap.remove("modelInfo");
                dataMap.put("result", dataMap.get("resultList"));
                dataMap.remove("resultList");

                Map<String, Object> pageMap = (Map) dataMap.get("pageParameter");
                pageMap.put("pageNo", pageMap.get("page"));
                pageMap.put("pageSize", pageMap.get("rows"));
                pageMap.remove("page");
                pageMap.remove("rows");
            }
            responseMsg.setResponseBody(dataMap);
            if (MapUtils.isNotEmpty(dataMap)) {
                List<Map<String, Object>> resultList = (List<Map<String, Object>>) dataMap.get("result");
                logDTO.setCallResult("查询数据" + resultList.size() + "条");
            }
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.SUCCESS, response, true);
        } catch (Exception e) {
            logger.error("call error", e);
            logDTO.setCallResult(e.getMessage());
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.SYSTEM_ERROR, response, true);
        }
    }

    private boolean validTokenAndUser(String token, HttpServletResponse response, ResponseMsg<Map<String, Object>> responseMsg,
                                      DssLogDTO logDTO, String requestUri, String dataResourceId) {

        if (StringUtils.isEmpty(token)) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.TOKEN_INVALID, response, true);
            return false;
        }
        // 从请求头获取token,查询用户信息，如果未查询到用户，提示用户【token不合法】
        DssUserDTO userQuery = new DssUserDTO();
        userQuery.setToken(token);

        try {
            DssUserDTO dssUserDTO = dssUserService.selectOne(userQuery);
            if (dssUserDTO == null) {
                recordCallStatus(responseMsg, logDTO, ApiCallStatus.TOKEN_INVALID, response, true);
                return false;
            }
            logDTO.setCallUserId(dssUserDTO.getUserId());
            logDTO.setCallUserName(dssUserDTO.getUserName());

            // 校验用户合法性和有效期,如果有效期过期，如果没有订阅或者未通过审核，提示用户【您没有调用该接口的权限】
            DssSubscribeDTO querySubscribe = new DssSubscribeDTO();
            querySubscribe.setUserId(dssUserDTO.getUserId());
            querySubscribe.setDataResourceId(dataResourceId);
            querySubscribe.setAuditStatus(AuditStatus.PASSED.getCode());
            Long subscribeState = subscribeService.selectCount(querySubscribe);
            if (subscribeState == null || subscribeState == 0) {
                recordCallStatus(responseMsg, logDTO, ApiCallStatus.USER_UNSUBSCRIBE, response, true);
                return false;
            }

            Date validDate = dssUserDTO.getValidDate();
            if (validDate != null && !DateUtils.compare(validDate, new Date())) {
                recordCallStatus(responseMsg, logDTO, ApiCallStatus.DATE_EXPIRED, response, true);
                return false;
            }

            // 检验用户日调用次数, 如果超过限制，提示用户 【日调用次数超过最大限制】
            if (dssUserDTO.getDayCount() != null && dssUserDTO.getDayCount() > 0) {
                String key = REDIS_PREFIX_CALL_API_NUM_TODAY + token + Constants.COLON + DateUtils.getSystemCurrentDate() + Constants.COLON + requestUri;
                Long cacheNum = redisCacheHelper.getAndIncrement(key, 60 * 60 * 24, TimeUnit.SECONDS);
                if (cacheNum > dssUserDTO.getDayCount()) {
                    recordCallStatus(responseMsg, logDTO, ApiCallStatus.CALL_DAY_MAX_LIMIT, response, true);
                    return true;
                }
            }
        } catch (Exception e) {
            logger.error("call error", e);
            logDTO.setCallResult(e.getMessage());
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.TOKEN_INVALID, response, true);
            return false;
        }

        return true;
    }


    private void recordCallStatus(ResponseMsg<Map<String, Object>> responseMsg, DssLogDTO logDTO, ApiCallStatus callStatus,
                                  HttpServletResponse response, boolean isReturn) {

        responseMsg.setRetCode(String.valueOf(callStatus.getCode()));

        logDTO.setId(ComUtil.getId());
        logDTO.setEndTime(new Date());
        logDTO.setCallStatus(callStatus.getCode() == 200 ? 1 : 0);
        if (StringUtils.isBlank(logDTO.getCallResult())) {
            logDTO.setCallResult(callStatus.getDescription());
        }

        responseMsg.setErrorDesc(logDTO.getCallResult());

        LogOperate logOperate = new LogOperate(logDTO);
        executor.submit(logOperate);

        if (isReturn) {
            JSONUtils.ajaxJson(JSONObject.toJSONString(responseMsg, JSONWriter.Feature.WriteMapNullValue), response);
        }
    }


    public class LogOperate implements Runnable {

        private final DssLogDTO log;

        LogOperate(DssLogDTO log) {
            this.log = log;
        }

        @Override
        public void run() {
            if (log != null) {
                try {
                    dssLogService.insert(log);
                } catch (Exception e) {
                    logger.error("异步插入日志异常", e);
                }
            }
        }
    }

}
